/*
 * Copyright (c) 2011 Gerwin Sturm, FoldedSoft e.U.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

 // Flags that can be set via the checkboxes
 
var DEBUGMODE = false;
var SMOOTH = true;
var NOCLIP = false;
var FASTUPDATES = false;
var MUSIC = false;
var THIRD_PERSON = true;

// Globals for WEBGL

var WEBGL_SUPPORT = false;
var ROOM_WIDTH = 1000;
var ROOM_HEIGHT = 150;
var ROOM_FLOOR = 0;

var renderer;
var scene;
var camera;
var lights = [];

var android_geometry = {};

var last_tick = 0;
var last_update = 0;

var PLAYER_SPEED = 0.08;
var ROT_SPEED = 0.1;
var PITCH_SPEED = 0.1;

// Globals for the shared state
var local_shared_state = null;
var local_participants = null;

// just some helpful functions to make things more readable...
var kbd = new keyboard_handler(window.document);

function player_id() {
  return gapi.hangout.getParticipantId();
}
function deg_to_rad(degrees) {
  return degrees * Math.PI / 180;
}

// shim layer with setTimeout fallback by Paul Irish - http://paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimFrame = (function(){
  return  window.requestAnimationFrame       || 
          window.webkitRequestAnimationFrame || 
          window.mozRequestAnimationFrame    || 
          window.oRequestAnimationFrame      || 
          window.msRequestAnimationFrame     || 
          function(/* function */ callback, /* DOMElement */ element){
            window.setTimeout(callback, 1000 / 60);
          };
})();

// ------------------------------------------------------------------------------------------
// Animations

var ANI_JUMP = 0;
var ANI_DUCK = 1;
var ANI_WAVE = 2;
var ANI_TURN = 3;
var ANI_HEADTURN = 4;

function Animation(k, n, s, l, f) {
  this.key = k;
  this.share_name = n;
  this.speed = s;
  this.length = l;
  this.func = f;
  this.int_count = 0;
  this.int_count_new = 0;
  this.chk_running = false;
  this.progress = 0;
}

Animation.prototype.animate = function(elapsed,player) {
  if(this.chk_running) {
    this.progress += this.speed*elapsed;
    if(this.progress>this.length) {
      this.chk_running = false;
      this.progress = 0;
    }
    this.func(player,this.progress);
  } else {
    if(this.int_count<this.int_count_new) {
      this.int_count++;
      this.chk_running = true;
      this.progress = 0;
    }
  }
}

// ------------------------------------------------------------------------------------------
// Audio

var AUDIO_SUPPORT = false;
var audio_context;
var audio_source;
var audio_gain_node;
var audio_analyser;
var audio_processor;
var audio_timer = 0;
var audio_bd = new BeatDetektor();
var audio_vu = new BeatDetektor.modules.vis.VU();

var songs = [];
songs[0] = {};
songs[0].buffer = null;
songs[0].song_title = "Emerge in Love";
songs[0].song_author = "Alex";
songs[0].song_link = "http://ccmixter.org/files/AlexBeroza/30239";
songs[0].mp3 = HOST + "songs/alex__emerge_in_love.mp3";
songs[0].ready = false;

songs[1] = {};
songs[1].buffer = null;
songs[1].song_title = "Straight to the Light";
songs[1].song_author = "Alex";
songs[1].song_link = "http://ccmixter.org/files/AlexBeroza/29630";
songs[1].mp3 = HOST + "songs/alex__straight_to_the_light.mp3";
songs[1].ready = false;

songs[2] = {};
songs[2].buffer = null;
songs[2].song_title = "Hornet";
songs[2].song_author = "George Ellinas";
songs[2].song_link = "http://ccmixter.org/files/George_Ellinas/15924";
songs[2].mp3 = HOST + "songs/george_ellinas__hornet.mp3";
songs[2].ready = false;

songs[3] = {};
songs[3].buffer = null;
songs[3].song_title = "Mine + Yours = Ours";
songs[3].song_author = "George Ellinas";
songs[3].song_link = "http://ccmixter.org/files/George_Ellinas/28107";
songs[3].mp3 = HOST + "songs/george_ellinas__mine_yours_ours.mp3";
songs[3].ready = false;

var current_song = 0;

function change_volume(element) {
  var fraction = parseInt(element.value) / parseInt(element.max);
  if(audio_gain_node) {
    audio_gain_node.gain.value = fraction * fraction;
  }
}

function initialize_audio() {
  AUDIO_SUPPORT = true;
  try {
    audio_context = new (window.AudioContext || window.webkitAudioContext)();
  } catch(e) {
    AUDIO_SUPPORT = false;
  }
  if(AUDIO_SUPPORT) {
    $("#hangoutdisco_music").append("Music <input type=\"checkbox\" id=\"hangoutdisco_control_music\" disabled=\"true\"> (All songs licensed under <a href=\"http://creativecommons.org/licenses/by/3.0/\" target=\"_blank\">CC BY 3.0</a>)<br>");
    $("#hangoutdisco_music").append("Volume <input type=\"range\" min=\"0\" max=\"100\" value=\"100\" id=\"hangoutdisco_control_volume\" onchange=\"change_volume(this);\" disabled=\"true\" /><br>");
    for(var i in songs) {
      $("#hangoutdisco_music").append("<input type=\"radio\" name=\"song\" value=\""+i+"\" id=\"song" + i+ "\" " + (i==0?"checked=\"checked\"":"") + " disabled=\"true\"> <a href=\"" + songs[i].song_link + "\" target=\"blank\">"+songs[i].song_title+" by " + songs[i].song_author + "</a><br>");
    }
    $("input[name='song']:radio").change(function() {
      for(var i in songs) {
        if($("#song"+i+":checked").val()!=undefined) {
          if(i!=current_song) {
            current_song = i;
            if(($("#hangoutdisco_control_music:checked").val()!=undefined)) {
              start_music();
            }
          }
        }
      }
    });
    $("#hangoutdisco_control_music").click(function() {
      MUSIC = ($("#hangoutdisco_control_music:checked").val()!=undefined);
      if(MUSIC) {
        start_music();
      } else {
        try { audio_source.noteOff(0); } catch(e) {}
        for(var i in lights) {
          lights[i].intensity = 2;
        }
      }
    });
  
    for(var i in songs) {
      load_song(i);
    }
  } else {
    $("#hangoutdisco_music").html("Unfortunately your browser doesn't support the new Web Audio API. Try Chrome ;)");
  }
}

function start_music() {
  try { audio_source.noteOff(0); } catch(e) {}
  audio_source = audio_context.createBufferSource();
  if(!audio_gain_node) audio_gain_node = audio_context.createGainNode();
  if(!audio_analyser) audio_analyser = audio_context.createAnalyser();
  if(!audio_processor) {
    audio_processor = audio_context.createJavaScriptNode(2048,1,1);
    audio_processor.onaudioprocess = function(e) {
      var input_array = e.inputBuffer.getChannelData(0);
      var freq_data = new Uint8Array(audio_analyser.frequencyBinCount);
      audio_analyser.getByteFrequencyData(freq_data);
      audio_bd.process(audio_context.currentTime, input_array);
      audio_timer += audio_bd.last_update;
      if (audio_timer > 1.0 / 24.0) {
        audio_vu.process(audio_bd, audio_timer);
        audio_timer = 0;
      }
      if (audio_vu.vu_levels.length) {
        var z = audio_vu.vu_levels[0];
        for(var i in lights) {
          lights[i].speed = 1.0 + 10*z;
        }
      }
      var bin_size = Math.floor(freq_data.length/4);
      for(var i=0;i<4;i++) {
        var sum = 0;
        for(var j=0;j<bin_size;j++) {
          sum+=freq_data[i*bin_size+j];
        }
        sum /= bin_size;
        lights[i].intensity = 0.5 + 3*sum/256;
      }
    }
  }
  audio_source.buffer = songs[current_song].buffer;
  audio_source.loop = true;
  
  audio_source.connect(audio_gain_node)
  audio_gain_node.connect(audio_context.destination);
  
  audio_source.connect(audio_analyser);
  audio_analyser.connect(audio_processor);
  audio_processor.connect(audio_context.destination);
  
  audio_source.noteOn(0);  
}

function load_song(s_nr) {
  var request = new XMLHttpRequest();
  request.open("GET", songs[s_nr].mp3, true);
  request.responseType = "arraybuffer";
  request.onload = function() {
    audio_context.decodeAudioData(request.response, function(buffer) {
      songs[s_nr].buffer = buffer;
      if(s_nr==0) {
        $("#hangoutdisco_control_music").removeAttr("disabled");
        $("#hangoutdisco_control_volume").removeAttr("disabled");
      }
      $("#song"+s_nr).removeAttr("disabled");
    }, function() {
      console.log("Error decoding: " + songs[s_nr].mp3);
    });
  }
  request.send();
}


// ------------------------------------------------------------------------------------------

var players = [];

// Player object
function Player(id,x,y,z,rot,pitch,col,s_width,s_height) {
  this.id = id;
  this.col = new THREE.Color(col);
  this.col_new = new THREE.Color(col);
  this.pos = new THREE.Vector3(x,y,z);
  this.pos_new = new THREE.Vector3(x,y,z);
  this.rot = rot;
  this.rot_new = rot;
  this.rot_rate = 0;
  this.pitch = pitch;
  this.pitch_new = pitch;
  this.pitch_rate = 0;
  this.speed = 0;
  this.strafe = 0;
  this.jogging_angle = 0;
  this.scale_width = s_width;
  this.scale_width_new = s_width;
  this.scale_height = s_height;
  this.scale_height_new = s_height;
  
  this.animations =[];
  
  this.animations[ANI_JUMP] = new Animation("space","jump",0.5,180,function(player,progress) {
    player.body.position.y = Math.sin(deg_to_rad(progress))*15;
  });
  this.animations[ANI_DUCK] = new Animation("C","duck",0.3,180,function(player,progress) {
    player.body.position.y = -Math.sin(deg_to_rad(progress))*15;
    player.leg1.position.y = 19+Math.sin(deg_to_rad(progress))*15;
    player.leg2.position.y = 19+Math.sin(deg_to_rad(progress))*15;
  });
  this.animations[ANI_WAVE] = new Animation("E","wave",0.5,360,function(player,progress) {
    player.arm2.rotation.x = 0; // don't swing arm while waving
    if(progress>90&&progress<270) {
      player.arm2.rotation.z = -(Math.PI*3/4 + Math.pow(Math.sin(deg_to_rad(progress)),2)*Math.PI/4);
    } else {
      player.arm2.rotation.z = -Math.pow(Math.sin(deg_to_rad(progress)),2)*Math.PI;
    }  
  });
  this.animations[ANI_TURN] = new Animation("T","turn",0.2,360,function(player,progress) {
    player.body.rotation.y = deg_to_rad(progress);
  });
  this.animations[ANI_HEADTURN] = new Animation("R","hturn",0.2,360,function(player,progress) {
    player.head.rotation.y = deg_to_rad(progress);
  });
  
  var player_material = new THREE.MeshLambertMaterial( { color: this.col.getHex(), shading: THREE.SmoothShading });
  var eyes_material = new THREE.MeshLambertMaterial( { color: 0xEEEEEE, shading: THREE.SmoothShading });

  var tmp_img = "";
  var p = gapi.hangout.getParticipantById(this.id);
  if(p.person.image) {
    tmp_img = p.person.image.url;
    tmp_img = tmp_img.replace("s96-c","s256-c");
    tmp_img = tmp_img.replace("?sz=50","");
    tmp_img += "?sz=256";              
  }
  var profile_material = player_material;
  if(tmp_img!="") {
    var profile_material = new THREE.MeshLambertMaterial({ map: THREE.ImageUtils.loadTexture(tmp_img) });
  }

  this.model = new THREE.Object3D();
  
  this.body = new THREE.Mesh(android_geometry.body, player_material);
  this.model.add(this.body);
  
  this.arm1 = new THREE.Mesh(android_geometry.arm, player_material);
  this.arm1.position.y = 45;
  this.arm1.position.x = 27;
  this.body.add(this.arm1);

  this.arm2 = new THREE.Mesh(android_geometry.arm, player_material);
  this.arm2.position.y = 45;
  this.arm2.position.x = -27;
  this.body.add(this.arm2);
  
  this.leg1 = new THREE.Mesh(android_geometry.leg, player_material);
  this.leg1.position.y = 19;
  this.leg1.position.x = 10;
  this.body.add(this.leg1);
  
  this.leg2 = new THREE.Mesh(android_geometry.leg, player_material);
  this.leg2.position.y = 19;
  this.leg2.position.x = -10;
  this.body.add(this.leg2);
  
  this.head = new THREE.Mesh(android_geometry.head, player_material);
  this.head.position.y = 52;
  this.eyes = new THREE.Mesh(android_geometry.eyes, eyes_material);
  this.head.add(this.eyes);
  this.body.add(this.head);
  
  this.profile_plane = new THREE.Mesh(new THREE.PlaneGeometry(20, 20, 1, 1), profile_material);
  this.profile_plane.position.y = 36;
  this.profile_plane.position.z = 22;
  this.body.add(this.profile_plane);
}

Player.prototype.set_state = function(s,v) {
  switch(s) {
   case "x": this.pos_new.x = parseFloat(v); break;
   case "y": this.pos_new.y = parseFloat(v); break;
   case "z": this.pos_new.z = parseFloat(v); break;
   case "rot": this.rot_new = parseFloat(v); break;
   case "pitch": this.pitch_new = parseFloat(v); break;
   case "col": this.col_new.setHex(parseInt(v)); break;
   case "s_width": this.scale_width_new = parseFloat(v); break;
   case "s_height": this.scale_height_new = parseFloat(v); break;
   default:
    if(s.substr(0,4)=="ani_") {
      for(var i=0;i<this.animations.length;i++) {
        if(this.animations[i].share_name==s.substr(4)) {
          this.animations[i].int_count_new = parseInt(v);
          break;
        }
      }
    }
  }
}

Player.prototype.animate = function(elapsed) {
  var chk_moving=false;

  if(this.id==player_id()) {
    // real animation based on speed values
    var dif_x = 0;
    var dif_z = 0;
    if(this.speed!=0||this.strafe!=0) {
      dif_x -= Math.sin(deg_to_rad(this.rot)) * this.speed * elapsed;
      dif_z -= Math.cos(deg_to_rad(this.rot)) * this.speed * elapsed;
      
      dif_x -= Math.sin(deg_to_rad(this.rot+90)) * this.strafe * elapsed;
      dif_z -= Math.cos(deg_to_rad(this.rot+90)) * this.strafe * elapsed;
    }
    
    // check for collision with other participants
    if(!NOCLIP) {
      for(var i=1;i<players.length;i++) {
        var test_x = players[i].pos.x;
        var test_z = players[i].pos.z;
        var test_xp = this.pos.x;
        var test_zp = this.pos.z;
        if(Math.sqrt((test_xp+dif_x-test_x)*(test_xp+dif_x-test_x)+(test_zp+dif_z-test_z)*(test_zp+dif_z-test_z))<60) {
          if(Math.sqrt((test_xp+dif_x-test_x)*(test_xp+dif_x-test_x)+(test_zp-test_z)*(test_zp-test_z))<60) {
            dif_x = 0;
          }
          if(Math.sqrt((test_xp-test_x)*(test_xp-test_x)+(test_zp+dif_z-test_z)*(test_zp+dif_z-test_z))<60) {
            dif_z = 0;
          }
        }
      }
    }
    if(dif_x!=0||dif_z!=0) {
      chk_moving = true;
    }
    this.pos.x += dif_x;
    this.pos.z += dif_z;
    
    this.rot += this.rot_rate * elapsed;
    if(this.rot>180) this.rot-=360;
    if(this.rot<-180) this.rot+=360;
    
    this.pitch += this.pitch_rate * elapsed;
    this.pitch = Math.max(-30,Math.min(30,this.pitch));
    this.pos.x = Math.max(-ROOM_WIDTH/2+25, Math.min(ROOM_WIDTH/2+25,this.pos.x));
    this.pos.z = Math.max(-ROOM_WIDTH/2+25, Math.min(ROOM_WIDTH/2+25,this.pos.z));
  } else {
    if(SMOOTH) {
      // interpolate from actual values to new values
      var dif_x = this.pos_new.x - this.pos.x;
      var dif_y = this.pos_new.y - this.pos.y;
      var dif_z = this.pos_new.z - this.pos.z;
      var dif_pitch = this.pitch_new - this.pitch;
      var dif_rot = this.rot_new - this.rot;
      var dif_s_width = this.scale_width_new - this.scale_width;
      var dif_s_height = this.scale_height_new - this.scale_height;

      if(dif_x!=0) {
        if(dif_x>-5&&dif_x<5) {
          this.pos.x = this.pos_new.x;
        } else {
          chk_moving = true;
          this.pos.x += 1.5*PLAYER_SPEED*dif_x/(Math.abs(dif_x)+Math.abs(dif_z))*elapsed;
        }
      }
      if(dif_z!=0) {
        if(dif_z>-5&&dif_z<5) {
          this.pos.z = this.pos_new.z;
        } else {
          chk_moving = true;
          this.pos.z += 1.5*PLAYER_SPEED*dif_z/(Math.abs(dif_x)+Math.abs(dif_z))*elapsed;
        }
      }      
      if(dif_y!=0) {
        if(dif_y>-5&&dif_y<5) {
          this.pos.y = this.pos_new.y;
        } else {
          this.pos.y += dif_y/Math.abs(dif_y)*2*PLAYER_SPEED*elapsed;
        }
      }

      if(dif_pitch!=0) {
        if(dif_pitch>-5&&dif_pitch<5) {
          this.pitch = this.pitch_new;
        } else {
          this.pitch += dif_pitch/Math.abs(dif_pitch)*0.1*elapsed;
        }
      }
      if(dif_rot!=0) {
        if(dif_rot>180) dif_rot-=360;
        if(dif_rot<-180) dif_rot+=360;
        if(dif_rot>-5&&dif_rot<5) {
          this.rot = this.rot_new;
        } else {
          this.rot += dif_rot/Math.abs(dif_rot)*0.1*elapsed;
        }
      } 
      if(dif_s_width!=0) {
        if(Math.abs(dif_s_width)<0.1) {
          this.scale_width = this.scale_width_new;
        } else {
          var change = dif_s_width/Math.abs(dif_s_width)*0.005*elapsed;
          if(Math.abs(change)>Math.abs(dif_s_width)) change = dif_s_width;
          this.scale_width += change;
        }
      }
      if(dif_s_height!=0) {
        if(Math.abs(dif_s_height)<0.1) {
          this.scale_height = this.scale_height_new;
        } else {
          var change = dif_s_height/Math.abs(dif_s_height)*0.005*elapsed;
          if(Math.abs(change)>Math.abs(dif_s_height)) change = dif_s_height;
          this.scale_height += change;
        }
      }      
    } else {
      this.pos.x = this.pos_new.x;
      this.pos.y = this.pos_new.y;
      this.pos.z = this.pos_new.z;
      this.rot = this.rot_new;
      this.pitch = this.pitch_new;
      this.scale_width = this.scale_width_new;
      this.scale_height = this.scale_height_new;
    }
  }

  // jogging animation
  if(chk_moving) {
    this.jogging_angle += elapsed * 0.5;
    if(this.jogging_angle>360) this.jogging_angle-=360;
  } else {
    if(this.jogging_angle!=0) {
      // just move back to normal position as quickly as possible
      var first_cycle = (this.jogging_angle<180);
      this.jogging_angle += elapsed*1.0;
      if(this.jogging_angle>(first_cycle?180:360)) {
        this.jogging_angle = 0;
      }
    }
  }
  var jogging = Math.sin(deg_to_rad(this.jogging_angle))
  this.body.position.y = jogging*jogging;
  this.arm1.rotation.x = jogging*deg_to_rad(30);
  this.arm2.rotation.x = -jogging*deg_to_rad(30);
  this.leg1.rotation.x = -jogging*deg_to_rad(30);
  this.leg2.rotation.x = jogging*deg_to_rad(30);  
  
  for(i=0;i<this.animations.length;i++) this.animations[i].animate(elapsed, this);
  
  this.model.scale.y = this.scale_height;
  this.model.scale.x = this.scale_width;
  this.model.scale.z = this.scale_width;
  
  if(this.col!=this.col_new) {
    this.col.r = this.col_new.r;
    this.col.g = this.col_new.g;
    this.col.b = this.col_new.b;
    var new_material = new THREE.MeshLambertMaterial( { color: this.col.getHex(), shading: THREE.SmoothShading });
    this.body.material = new_material;
    this.head.material = new_material;
    this.leg1.material = new_material;
    this.leg2.material = new_material;
    this.arm1.material = new_material;
    this.arm2.material = new_material;
  }
  
  try {
    var v = gapi.hangout.av.getParticipantVolume(this.id)
  } catch(e) {}
  if(v) {
    this.head.position.y = 52+v;
  } else {
    this.head.position.y = 52;
  }
  
  this.model.position.x = this.pos.x;
  this.model.position.y = this.pos.y;
  this.model.position.z = this.pos.z;
  var phi = deg_to_rad(90 - this.pitch);
  var phi2 = deg_to_rad(90 - this.pitch/2);
  var theta = deg_to_rad(this.rot);
  var target_x = this.pos.x - 100 * Math.sin(theta);
  if(this.pitch<0) {
    var target_y = this.pos.y + 100 * Math.cos(phi);
  } else {
    var target_y = this.pos.y + 100 * Math.cos(phi2);
  }
  var target_z = this.pos.z - 100 * Math.cos(theta);
  if(this.pitch<0) {
    this.head.rotation.x = 0;
    this.leg1.rotation.x += deg_to_rad(this.pitch);
    this.leg2.rotation.x += deg_to_rad(this.pitch);
  } else {
    this.head.rotation.x = deg_to_rad(-this.pitch/2);
    this.leg1.rotation.x += deg_to_rad(this.pitch/2);
    this.leg2.rotation.x += deg_to_rad(this.pitch/2);          
  }
  this.model.lookAt(new THREE.Vector3(target_x,target_y,target_z));
}

Player.prototype.set_display = function(d) {
  this.body.visible = d;
  this.arm1.visible = d;
  this.arm2.visible = d;
  this.leg1.visible = d;
  this.leg2.visible = d;
  this.head.visible = d;
  this.eyes.visible = d;
}

// ------------------------------------------------------------------------------------------

function change_avatar() {
  if(players[0]) {
    players[0].col_new.r = $("#hangoutdisco_avatar_color_r").val()/100;
    players[0].col_new.g = $("#hangoutdisco_avatar_color_g").val()/100;
    players[0].col_new.b = $("#hangoutdisco_avatar_color_b").val()/100;
    players[0].scale_height = 1.0 + ($("#hangoutdisco_avatar_height").val()-50)/100;
    players[0].scale_width = 1.0 + ($("#hangoutdisco_avatar_width").val()-50)/100;
  }
}

function initialize_app() {
  WEBGL_SUPPORT = !!window.WebGLRenderingContext;
  
  // initialize WebGL
  if(WEBGL_SUPPORT) {
    try {
      renderer = new THREE.WebGLRenderer();
      renderer.setSize(500, 350);
      renderer.setClearColorHex(0,1);

      scene = new THREE.Scene();
      camera = new THREE.PerspectiveCamera(45, 500/350, 0.1, 10000);
      scene.add(camera);

      var colors = [0x3333FF, 0xFFFF33, 0x33FF33, 0xFF3333];
      
      for(var i=0;i<4;i++) {
        var tmp_light = new THREE.PointLight(colors[i]);
        tmp_light.position.x = ((i%2==0)?1:-1)*(ROOM_WIDTH/4);
        tmp_light.position.y = ROOM_FLOOR+ROOM_HEIGHT*0.3;
        tmp_light.position.z = ((i<2)?1:-1)*(ROOM_WIDTH/4);
        lights.push(tmp_light);
      }
      
      for(var i in lights) {
        scene.add(lights[i]);
        lights[i].dir = Math.random()*360-180;
        lights[i].speed = 0.5;
        lights[i].intensity = 2;
      }
      
      var room_material = new THREE.MeshLambertMaterial({color: 0xCCCCCC});
      var floor = new THREE.Mesh(new THREE.PlaneGeometry(ROOM_WIDTH, ROOM_WIDTH, 50, 50), room_material);
      floor.rotation.x = -Math.PI/2;
      floor.position.y = ROOM_FLOOR;
      scene.add(floor);
      var ceiling = new THREE.Mesh(new THREE.PlaneGeometry(ROOM_WIDTH, ROOM_WIDTH, 50, 50), room_material);
      ceiling.rotation.x = Math.PI/2;
      ceiling.position.y = ROOM_FLOOR+ROOM_HEIGHT;
      scene.add(ceiling);
      var wall_material = new THREE.MeshLambertMaterial({color: 0x222222});
      for(var i=0;i<4;i++) {
        var wall = new THREE.Mesh(new THREE.PlaneGeometry(ROOM_WIDTH, ROOM_HEIGHT, 50, 10), wall_material);
        wall.rotation.y = Math.PI*i/2;
        wall.position.x = (i==3)?ROOM_WIDTH/2:((i==1)?-ROOM_WIDTH/2:0);
        wall.position.y = ROOM_FLOOR + ROOM_HEIGHT/2;
        wall.position.z = (i==2)?ROOM_WIDTH/2:((i==0)?-ROOM_WIDTH/2:0);
        scene.add(wall);
      }
    } catch(e) {
      console.log(e.message);
      WEBGL_SUPPORT = false;
    }
  }
  $("body").html("<div id=\"hangoutdisco\"></div>");
  if(WEBGL_SUPPORT) {
    $("#hangoutdisco").append("<div id=\"hangoutdisco_main\" style=\"float:left;\"></div>");
    $("#hangoutdisco_main").append(renderer.domElement);
    $("#hangoutdisco").append("<div id=\"hangoutdisco_avatar\" style=\"border:1px solid black; float:left; padding:5px; margin:2px; text-align: right;\"></div>");
    $("#hangoutdisco").append("<div id=\"hangoutdisco_music\" style=\"border:1px solid black; float:left; padding:5px; margin:2px;\"></div>");
    $("#hangoutdisco").append("<div id=\"hangoutdisco_controls\" style=\"border:1px solid black; float:left; padding:5px; margin:2px;\"></div>");
    $("#hangoutdisco").append("<div id=\"hangoutdisco_debug\" style=\"float:left; padding:5px;\"></div>");
    
    $("#hangoutdisco_avatar").append("<b>Avatar settings</b><br>");
    $("#hangoutdisco_avatar").append("R <input type=\"range\" min=\"0\" max=\"100\" value=\"100\" id=\"hangoutdisco_avatar_color_r\" onchange=\"change_avatar();\" style=\"width:50px;\"><br>");
    $("#hangoutdisco_avatar").append("G <input type=\"range\" min=\"0\" max=\"100\" value=\"100\" id=\"hangoutdisco_avatar_color_g\" onchange=\"change_avatar();\" style=\"width:50px;\"><br>");
    $("#hangoutdisco_avatar").append("B <input type=\"range\" min=\"0\" max=\"100\" value=\"100\" id=\"hangoutdisco_avatar_color_b\" onchange=\"change_avatar();\" style=\"width:50px;\"><br>");    
    $("#hangoutdisco_avatar").append("Height <input type=\"range\" min=\"0\" max=\"100\" value=\"50\" id=\"hangoutdisco_avatar_height\" onchange=\"change_avatar();\" style=\"width:50px;\"><br>");
    $("#hangoutdisco_avatar").append("Width <input type=\"range\" min=\"0\" max=\"100\" value=\"50\" id=\"hangoutdisco_avatar_width\" onchange=\"change_avatar();\" style=\"width:50px;\"><br>");
    

    $("#hangoutdisco_controls").append("3rd person view <input type=\"checkbox\" id=\"hangoutdisco_control_third_person\" checked=\"checked\"/><br>");
    $("#hangoutdisco_control_third_person").click(function() {
      THIRD_PERSON = ($("#hangoutdisco_control_third_person:checked").val()!=undefined);
    });
    $("#hangoutdisco_controls").append("Smooth transitions <input type=\"checkbox\" id=\"hangoutdisco_control_smooth\" checked=\"checked\" /><br>");
    $("#hangoutdisco_control_smooth").click(function() {
      SMOOTH = ($("#hangoutdisco_control_smooth:checked").val()!=undefined);
    });
    $("#hangoutdisco_controls").append("No collisions <input type=\"checkbox\" id=\"hangoutdisco_control_noclip\" /><br>");
    $("#hangoutdisco_control_noclip").click(function() {
      NOCLIP = ($("#hangoutdisco_control_noclip:checked").val()!=undefined);
    });    
    $("#hangoutdisco_controls").append("Debug mode <input type=\"checkbox\" id=\"hangoutdisco_control_debug\" /><br>");
    $("#hangoutdisco_control_debug").click(function() {
      DEBUGMODE = ($("#hangoutdisco_control_debug:checked").val()!=undefined);
    });
    $("#hangoutdisco_controls").append("Fast updates <input type=\"checkbox\" id=\"hangoutdisco_control_fast\" /><br>");
    $("#hangoutdisco_control_fast").click(function() {
      FASTUPDATES = ($("#hangoutdisco_control_fast:checked").val()!=undefined);
    });

    initialize_audio();

    var loader = new THREE.JSONLoader();
    loader.load(HOST + "models/android_body.js", function(geometry) {
      android_geometry.body = geometry;
      android_geometry.body.computeFaceNormals();
      android_geometry.body.computeVertexNormals();
      
      var loader = new THREE.JSONLoader();
      loader.load(HOST + "models/android_arm.js", function(geometry) {
        android_geometry.arm = geometry;
        android_geometry.arm.computeFaceNormals();
        android_geometry.arm.computeVertexNormals();      
      
        var loader = new THREE.JSONLoader();
        loader.load(HOST + "models/android_leg_v2.js", function(geometry) {
          android_geometry.leg = geometry;
          android_geometry.leg.computeFaceNormals();
          android_geometry.leg.computeVertexNormals();      
        
          var loader = new THREE.JSONLoader();
          loader.load(HOST + "models/android_head.js", function(geometry) {
            android_geometry.head = geometry;
            android_geometry.head.computeFaceNormals();
            android_geometry.head.computeVertexNormals();      
          
            var loader = new THREE.JSONLoader();
            loader.load(HOST + "models/android_eyes.js", function(geometry) {
              android_geometry.eyes = geometry;
              android_geometry.eyes.computeFaceNormals();
              android_geometry.eyes.computeVertexNormals();
              initialize_player();
              update_shared_state(true);
              tick();
            });
          });
        });
      });
    });
  } else {
    $("#hangoutdisco").append("Unfortunately WebGL isn't supported by your browser. Please update to a modern browser.");
  }
}

function initialize_player() {
  var place_found = false;
  var x = 0;
  var y = 0;
  var z = 0;
  var col = Math.floor(((Math.random()*100+155)*256+(Math.random()*100+155))*256 +(Math.random()*100+155));
  while(!place_found) {
    x = Math.random()*ROOM_WIDTH-ROOM_WIDTH/2;
    y = 0;
    z = Math.random()*ROOM_WIDTH-ROOM_WIDTH/2;  
    place_found = true;
    for(var i in local_participants) {
      var tmp_id = local_participants[i].id;
      if(tmp_id!=player_id()) {
        if(local_shared_state[tmp_id+":x"]) {
          test_x = local_shared_state[tmp_id+":x"];
          test_z = local_shared_state[tmp_id+":z"];
          if(Math.sqrt((x-test_x)*(x-test_x)+(z-test_z)*(z-test_z))<100) {
            place_found = false;
            break;
          }
        }
      }
    }
  }
  players[0] = new Player(player_id(),x,y,z,0,0,col,1,1);
  scene.add(players[0].model);
  $("#hangoutdisco_avatar_color_r").val(Math.floor(players[0].col.r*100));
  $("#hangoutdisco_avatar_color_g").val(Math.floor(players[0].col.g*100));
  $("#hangoutdisco_avatar_color_b").val(Math.floor(players[0].col.b*100));
}


function update_local_shared_state() {
  local_shared_state = gapi.hangout.data.getState();
  for(var i in local_shared_state) {
    var tmp = i.split(":");
    for(var j=1;j<players.length;j++) {
      if(players[j].id==tmp[0]) {
        players[j].set_state(tmp[1],local_shared_state[i]);
        break;
      }
    }
  }
}

function update_shared_state(force) {
  var this_update = new Date().getTime();
  if((last_update!=0)||force) {
    if((this_update-last_update>(FASTUPDATES?33:1000))||force) {
      update_local_shared_state();
      
      var new_state = {};
      var delete_state = [];
      for(var i in local_shared_state) {
        var tmp = i.split(":");
        var chk_found = false;
        for(var p in local_participants) {
          if(local_participants[p].id==tmp[0]) {
            chk_found = true;
            break;
          }
        }
        if(!chk_found) {
          delete_state.push(i);
        }
      }
      var this_player_id = player_id();
      var this_player = players[0];
      
      new_state[this_player_id+":col"] = this_player.col.getHex().toString();
      new_state[this_player_id+":x"] = Math.floor(this_player.pos.x).toString();
      new_state[this_player_id+":y"] = Math.floor(this_player.pos.y).toString();
      new_state[this_player_id+":z"] = Math.floor(this_player.pos.z).toString();
      new_state[this_player_id+":pitch"] = Math.floor(this_player.pitch).toString();
      new_state[this_player_id+":rot"] = Math.floor(this_player.rot).toString();
      new_state[this_player_id+":s_width"] = this_player.scale_width.toString();
      new_state[this_player_id+":s_height"] = this_player.scale_height.toString();
      for(var i=0;i<this_player.animations.length;i++) {
        new_state[this_player_id+":ani_"+this_player.animations[i].share_name] = this_player.animations[i].int_count.toString();
      }
      
      gapi.hangout.data.submitDelta(new_state, delete_state);
      last_update = this_update;
    }
  } else {
    last_update = this_update;
  }
}

function handle_keys() {
  var this_player = players[0];
  
  for(var i=0;i<this_player.animations.length;i++) {
    if(kbd.pressed(this_player.animations[i].key)) {
      if(!this_player.animations[i].chk_running) {
        this_player.animations[i].int_count_new++;
      }
    }
  }
  
  if (kbd.pressed("up")) {
    this_player.pitch_rate = PITCH_SPEED;
  } else if (kbd.pressed("down")) {
    this_player.pitch_rate = -PITCH_SPEED;
  } else {
    this_player.pitch_rate = 0;
  }

  if (kbd.pressed("left")) {
    this_player.rot_rate = ROT_SPEED;
  } else if (kbd.pressed("right")) {
    this_player.rot_rate = -ROT_SPEED;
  } else {
    this_player.rot_rate = 0;
  }

  if (kbd.pressed("a")) {
    this_player.strafe = PLAYER_SPEED/2;
  } else if (kbd.pressed("d")) {
    this_player.strafe = -PLAYER_SPEED/2;
  } else {
    this_player.strafe = 0;
  }
  
  if (kbd.pressed("w")) {
    this_player.speed = PLAYER_SPEED;
  } else if (kbd.pressed("s")) {
    this_player.speed = -PLAYER_SPEED;
  } else {
    this_player.speed = 0;
  }
  if (kbd.pressed("shift")) {
    this_player.speed *= 2;
  }
}

// most of the work gets done here...

function animate() {
  var this_tick = new Date().getTime();
  if (last_tick != 0) {
    var elapsed = this_tick - last_tick;
    //players
    for(var i=0;i<players.length;i++) {
      players[i].animate(elapsed);
    }
    
    //camera
    var this_player = players[0];
    var phi = deg_to_rad(90 - this_player.pitch);
    var phi2 = deg_to_rad(90 - this_player.pitch/2);
    var theta = deg_to_rad(this_player.rot); 
    var theta2 = deg_to_rad(this_player.rot+90);  
    
    if(THIRD_PERSON) {
      this_player.set_display(true);
      camera.position.x = this_player.pos.x + 150 * Math.sin(theta);
      camera.position.y = 60;
      camera.position.z = this_player.pos.z + 150 * Math.cos(theta);
      camera.position.x += 25*Math.sin(theta2);
      camera.position.z += 25*Math.cos(theta2);
      if(camera.position.x<=-ROOM_WIDTH/2) camera.position.x = -ROOM_WIDTH/2+1;
      if(camera.position.x>=ROOM_WIDTH/2) camera.position.x = ROOM_WIDTH/2-1;
      if(camera.position.z<=-ROOM_WIDTH/2) camera.position.z = -ROOM_WIDTH/2+1;
      if(camera.position.z>=ROOM_WIDTH/2) camera.position.z = ROOM_WIDTH/2-1; 
    } else {
      this_player.set_display(false);
      camera.position.x = this_player.pos.x;
      camera.position.y = this_player.body.position.y+60;
      camera.position.z = this_player.pos.z;
    }

    var target_x = camera.position.x - 100 * Math.sin(theta);
    var target_y = camera.position.y + 100 * Math.cos(phi);
    var target_z = camera.position.z - 100 * Math.cos(theta);
    camera.lookAt(new THREE.Vector3(target_x,target_y,target_z));
    
    //lights
    for(var i in lights) {
      var dif_x = 0;
      var dif_z = 0;
      if(lights[i].speed!=0) {
        dif_x -= Math.sin(deg_to_rad(lights[i].dir)) * lights[i].speed * elapsed;
        dif_z -= Math.cos(deg_to_rad(lights[i].dir)) * lights[i].speed * elapsed;        
      }
      lights[i].position.x +=dif_x;
      lights[i].position.z +=dif_z;
      if(lights[i].position.x>ROOM_WIDTH*0.45) {
        lights[i].position.x = ROOM_WIDTH*0.45;
        lights[i].dir = Math.random()*360-180;
      }
      if(lights[i].position.x<-ROOM_WIDTH*0.45) {
        lights[i].position.x = -ROOM_WIDTH*0.45;
        lights[i].dir = Math.random()*360-180;
      }
      if(lights[i].position.z>ROOM_WIDTH*0.45) {
        lights[i].position.z = ROOM_WIDTH*0.45;
        lights[i].dir = Math.random()*360-180;
      }
      if(lights[i].position.z<-ROOM_WIDTH*0.45) {
        lights[i].position.z = -ROOM_WIDTH*0.45;
        lights[i].dir = Math.random()*360-180;
      }
      if(lights[i].speed>0.5) lights[i].speed -= 0.01*elapsed;
      if(lights[i].speed<0.5) lights[i].speed = 0.5;
      if(!MUSIC) {
        lights[i].intensity = 2;
      }
    }    
  }  
  last_tick = this_tick;
}

function check_participants() {
 
  for(var i=1;i<players.length;i++) {
    players[i].chk_found = false;
  }
  
  for(var i in local_participants) {
    var tmp_id = local_participants[i].id;
    if(tmp_id!=player_id()) {
      var chk_new = true;
      for(var j=1;j<players.length;j++) {
        if(players[j].id==tmp_id) {
          chk_new = false;
          players[j].chk_found = true;
          break;
        }
      }
      if(chk_new) {
        if(local_shared_state[tmp_id+":x"]) {
          if(local_participants[i].person) {
            var x = 0; if(local_shared_state[tmp_id+":x"]) x = parseFloat(local_shared_state[tmp_id+":x"]);
            var y = 0; if(local_shared_state[tmp_id+":y"]) y = parseFloat(local_shared_state[tmp_id+":y"]);
            var z = 0; if(local_shared_state[tmp_id+":z"]) z = parseFloat(local_shared_state[tmp_id+":z"]);
            var rot = 0; if(local_shared_state[tmp_id+":rot"]) rot = parseFloat(local_shared_state[tmp_id+":rot"]);
            var pitch = 0; if(local_shared_state[tmp_id+":pitch"]) pitch = parseFloat(local_shared_state[tmp_id+":pitch"]);
            var col = 0; if(local_shared_state[tmp_id+":col"]) col = parseInt(local_shared_state[tmp_id+":col"]);
            var s_width = 0; if(local_shared_state[tmp_id+":s_width"]) s_width = parseFloat(local_shared_state[tmp_id+":s_width"]);
            var s_height = 0; if(local_shared_state[tmp_id+":s_height"]) s_height = parseFloat(local_shared_state[tmp_id+":s_height"]);            
            var new_player = new Player(tmp_id,x,y,z,rot,pitch,col,s_width,s_height);
            for(var i=0; i<new_player.animations.length;i++) {
              var count = 0;
              if(local_shared_state[tmp_id+":ani_"+new_player.animations[i].share_name])
                count = parseInt(local_shared_state[tmp_id+":ani_"+new_player.animations[i].share_name]);
              new_player.animations[i].int_count = count;
              new_player.animations[i].int_count_new = count;
            }
            players.push(new_player);
            scene.add(new_player.model);
            new_player.chk_found = true;
          }
        }
      }
    }
  }
 
  var i = 1;
  while(i < players.length) {
    if(players[i].chk_found) {
      i++;
    } else {
      var removed_player = players.splice(i,1);
      scene.remove(removed_player[0].model);
      delete removed_player[0];
    }
  }
}

// game loop
function tick() {
  if (local_shared_state && local_participants && ($("#hangoutdisco")) && camera && scene && renderer) {
    update_shared_state(false);
    check_participants();
    handle_keys();
    animate();
    renderer.render(scene, camera);
    
    $("#hangoutdisco_debug").html("");
    if(DEBUGMODE) {
      for(var i in local_shared_state) {
        $("#hangoutdisco_debug").append(i + ": " + local_shared_state[i] + "<br>");
      }
    }
  }
  requestAnimFrame(tick);
}

// let's get this party started
(function() {
  if (gapi && gapi.hangout) {
    var initHangout = function(apiInitEvent) {
      if (apiInitEvent.isApiReady) {
        gapi.hangout.onParticipantsChanged.add(function(partChangeEvent) {
          local_participants = gapi.hangout.getParticipants();
        });
        if (!local_shared_state) {
          local_shared_state = gapi.hangout.data.getState();
        }
        if (!local_participants) {
          local_participants = gapi.hangout.getParticipants();
        }
        initialize_app();
        gapi.hangout.onApiReady.remove(initHangout);
      }
    };
    gapi.hangout.onApiReady.add(initHangout);
  }
})();
